#include "wx/wxprec.h"
#include "wx/zipstrm.h"
#include "wx/datstrm.h"
#include "wx/zstream.h"
#include "wx/mstream.h"
#include "wx/ptr_scpd.h"
#include "wx/wfstream.h"
#include "zlib.h"

enum {
  VERSION_NEEDED_TO_EXTRACT = 20
};

enum {
  CENTRAL_MAGIC = 0x02014b50,     // central directory record
  LOCAL_MAGIC   = 0x04034b50,     // local header
  END_MAGIC     = 0x06054b50,     // end of central directory record
  SUMS_MAGIC    = 0x08074b50      // data descriptor (info-zip)
};

enum {
  wxZIP_S_IFMT  = 0xF0000000,
  wxZIP_S_IFDIR = 0x40000000,
  wxZIP_S_IFREG = 0x80000000
};

// minimum sizes for the various records
enum {
  CENTRAL_SIZE  = 46,
  LOCAL_SIZE    = 30,
  END_SIZE      = 22,
  SUMS_SIZE     = 12
};

enum {
  OUTPUT_LATENCY = 4096
};

// Some offsets into the local header
enum {
  SUMS_OFFSET  = 14
};

IMPLEMENT_DYNAMIC_CLASS( wxZipEntry, wxArchiveEntry )
IMPLEMENT_DYNAMIC_CLASS( wxZipClassFactory, wxArchiveClassFactory )


static wxString ReadString( wxInputStream& stream, wxUint16 len, wxMBConv& conv ) {
  if( len == 0 ) {
    return wxEmptyString;
  }
  wxCharBuffer buf( len );
  stream.Read( buf.data(), len );
  wxString str( buf, conv );
  return str;
}

static inline wxUint32 CrackUint32( const char *m ) {
  const unsigned char *n = ( const unsigned char* )m;
  return ( n[3] << 24 ) | ( n[2] << 16 ) | ( n[1] << 8 ) | n[0];
}

static inline wxUint16 CrackUint16( const char *m ) {
  const unsigned char *n = ( const unsigned char* )m;
  return ( n[1] << 8 ) | n[0];
}

static wxFileOffset QuietSeek( wxInputStream& stream, wxFileOffset pos ) {
  #if defined(__WXDEBUG__) && wxUSE_LOG
  wxLogLevel level = wxLog::GetLogLevel();
  wxLog::SetLogLevel( wxLOG_Debug - 1 );
  wxFileOffset result = stream.SeekI( pos );
  wxLog::SetLogLevel( level );
  return result;
  #else
  return stream.SeekI( pos );
  #endif
}



wxZipClassFactory g_wxZipClassFactory;

wxZipClassFactory::wxZipClassFactory() {
  if( this == &g_wxZipClassFactory ) {
    PushFront();
  }
}

const wxChar * const * wxZipClassFactory::GetProtocols( wxStreamProtocolType type ) const {
  static const wxChar *protocols[] = { _T( "zip" ), NULL };
  static const wxChar *mimetypes[] = { _T( "application/zip" ), NULL };
  static const wxChar *fileexts[]  = { _T( ".zip" ), _T( ".htb" ), NULL };
  static const wxChar *empty[] = { NULL };
  switch( type ) {
    case wxSTREAM_PROTOCOL:
      return protocols;
    case wxSTREAM_MIMETYPE:
      return mimetypes;
    case wxSTREAM_FILEEXT:
      return fileexts;
    default:
      return empty;
  }
}

class wxZipHeader {
  public:
    wxZipHeader( wxInputStream& stream, size_t size );

    inline wxUint8 Read8();
    inline wxUint16 Read16();
    inline wxUint32 Read32();

    const char *GetData() const             { return m_data; }
    size_t GetSize() const                  { return m_size; }
    operator bool() const                   { return m_ok; }

    size_t Seek( size_t pos )                 { m_pos = pos; return m_pos; }
    size_t Skip( size_t size )                { m_pos += size; return m_pos; }

    wxZipHeader& operator>>( wxUint8& n )     { n = Read8();  return *this; }
    wxZipHeader& operator>>( wxUint16& n )    { n = Read16(); return *this; }
    wxZipHeader& operator>>( wxUint32& n )    { n = Read32(); return *this; }

  private:
    char m_data[64];
    size_t m_size;
    size_t m_pos;
    bool m_ok;
};

wxZipHeader::wxZipHeader( wxInputStream& stream, size_t size )
  : m_size( 0 ),
    m_pos( 0 ),
    m_ok( false ) {
  wxCHECK_RET( size <= sizeof( m_data ), _T( "buffer too small" ) );
  m_size = stream.Read( m_data, size ).LastRead();
  m_ok = m_size == size;
}

inline wxUint8 wxZipHeader::Read8() {
  wxASSERT( m_pos < m_size );
  return m_data[m_pos++];
}

inline wxUint16 wxZipHeader::Read16() {
  wxASSERT( m_pos + 2 <= m_size );
  wxUint16 n = CrackUint16( m_data + m_pos );
  m_pos += 2;
  return n;
}

inline wxUint32 wxZipHeader::Read32() {
  wxASSERT( m_pos + 4 <= m_size );
  wxUint32 n = CrackUint32( m_data + m_pos );
  m_pos += 4;
  return n;
}

class wxStoredInputStream : public wxFilterInputStream {
  public:
    wxStoredInputStream( wxInputStream& stream );

    void Open( wxFileOffset len ) { Close(); m_len = len; }
    void Close() { m_pos = 0; m_lasterror = wxSTREAM_NO_ERROR; }

    virtual char Peek() { return wxInputStream::Peek(); }
    virtual wxFileOffset GetLength() const { return m_len; }

  protected:
    virtual size_t OnSysRead( void *buffer, size_t size );
    virtual wxFileOffset OnSysTell() const { return m_pos; }

  private:
    wxFileOffset m_pos;
    wxFileOffset m_len;

    DECLARE_NO_COPY_CLASS( wxStoredInputStream )
};

wxStoredInputStream::wxStoredInputStream( wxInputStream& stream )
  : wxFilterInputStream( stream ),
    m_pos( 0 ),
    m_len( 0 ) {
}

size_t wxStoredInputStream::OnSysRead( void *buffer, size_t size ) {
  size_t count = wx_truncate_cast( size_t,
                                   wxMin( size + wxFileOffset( 0 ), m_len - m_pos + size_t( 0 ) ) );
  count = m_parent_i_stream->Read( buffer, count ).LastRead();
  m_pos += count;
  if( count < size ) {
    m_lasterror = m_pos == m_len ? wxSTREAM_EOF : wxSTREAM_READ_ERROR;
  }
  return count;
}

class wxStoredOutputStream : public wxFilterOutputStream {
  public:
    wxStoredOutputStream( wxOutputStream& stream ) :
      wxFilterOutputStream( stream ), m_pos( 0 ) { }

    bool Close() {
      m_pos = 0;
      m_lasterror = wxSTREAM_NO_ERROR;
      return true;
    }

  protected:
    virtual size_t OnSysWrite( const void *buffer, size_t size );
    virtual wxFileOffset OnSysTell() const { return m_pos; }

  private:
    wxFileOffset m_pos;
    DECLARE_NO_COPY_CLASS( wxStoredOutputStream )
};

size_t wxStoredOutputStream::OnSysWrite( const void *buffer, size_t size ) {
  if( !IsOk() || !size ) {
    return 0;
  }
  size_t count = m_parent_o_stream->Write( buffer, size ).LastWrite();
  if( count != size ) {
    m_lasterror = wxSTREAM_WRITE_ERROR;
  }
  m_pos += count;
  return count;
}

class wxTeeInputStream : public wxFilterInputStream {
  public:
    wxTeeInputStream( wxInputStream& stream );

    size_t GetCount() const { return m_end - m_start; }
    size_t GetData( char *buffer, size_t size );

    void Open();
    bool Final();

    wxInputStream& Read( void *buffer, size_t size );

  protected:
    virtual size_t OnSysRead( void *buffer, size_t size );
    virtual wxFileOffset OnSysTell() const { return m_pos; }

  private:
    wxFileOffset m_pos;
    wxMemoryBuffer m_buf;
    size_t m_start;
    size_t m_end;

    DECLARE_NO_COPY_CLASS( wxTeeInputStream )
};

wxTeeInputStream::wxTeeInputStream( wxInputStream& stream )
  : wxFilterInputStream( stream ),
    m_pos( 0 ), m_buf( 8192 ), m_start( 0 ), m_end( 0 ) {
}

void wxTeeInputStream::Open() {
  m_pos = m_start = m_end = 0;
  m_lasterror = wxSTREAM_NO_ERROR;
}

bool wxTeeInputStream::Final() {
  bool final = m_end == m_buf.GetDataLen();
  m_end = m_buf.GetDataLen();
  return final;
}

wxInputStream& wxTeeInputStream::Read( void *buffer, size_t size ) {
  size_t count = wxInputStream::Read( buffer, size ).LastRead();
  m_end = m_buf.GetDataLen();
  m_buf.AppendData( buffer, count );
  return *this;
}

size_t wxTeeInputStream::OnSysRead( void *buffer, size_t size ) {
  size_t count = m_parent_i_stream->Read( buffer, size ).LastRead();
  if( count < size ) {
    m_lasterror = m_parent_i_stream->GetLastError();
  }
  return count;
}

size_t wxTeeInputStream::GetData( char *buffer, size_t size ) {
  if( m_wbacksize ) {
    size_t len = m_buf.GetDataLen();
    len = len > m_wbacksize ? len - m_wbacksize : 0;
    m_buf.SetDataLen( len );
    if( m_end > len ) {
      wxFAIL;
      m_end = len;
    }
    m_parent_i_stream->Reset();
    m_parent_i_stream->Ungetch( m_wback, m_wbacksize );
    free( m_wback );
    m_wback = NULL;
    m_wbacksize = 0;
    m_wbackcur = 0;
  }
  if( size > GetCount() ) {
    size = GetCount();
  }
  if( size ) {
    memcpy( buffer, m_buf + m_start, size );
    m_start += size;
    wxASSERT( m_start <= m_end );
  }
  if( m_start == m_end && m_start > 0 && m_buf.GetDataLen() > 0 ) {
    size_t len = m_buf.GetDataLen();
    char *buf = ( char* )m_buf.GetWriteBuf( len );
    len -= m_end;
    memmove( buf, buf + m_end, len );
    m_buf.UngetWriteBuf( len );
    m_start = m_end = 0;
  }
  return size;
}

class wxRawInputStream : public wxFilterInputStream {
  public:
    wxRawInputStream( wxInputStream& stream );
    virtual ~wxRawInputStream() { delete m_tee; }

    wxInputStream* Open( wxInputStream *decomp );
    wxInputStream& GetTee() const { return *m_tee; }

  protected:
    virtual size_t OnSysRead( void *buffer, size_t size );
    virtual wxFileOffset OnSysTell() const { return m_pos; }

  private:
    wxFileOffset m_pos;
    wxTeeInputStream *m_tee;

    enum { BUFSIZE = 8192 };
    wxCharBuffer m_dummy;

    DECLARE_NO_COPY_CLASS( wxRawInputStream )
};

wxRawInputStream::wxRawInputStream( wxInputStream& stream )
  : wxFilterInputStream( stream ),
    m_pos( 0 ),
    m_tee( new wxTeeInputStream( stream ) ),
    m_dummy( BUFSIZE ) {
}

wxInputStream *wxRawInputStream::Open( wxInputStream *decomp ) {
  if( decomp ) {
    m_parent_i_stream = decomp;
    m_pos = 0;
    m_lasterror = wxSTREAM_NO_ERROR;
    m_tee->Open();
    return this;
  } else
  { return NULL; }
}

size_t wxRawInputStream::OnSysRead( void *buffer, size_t size ) {
  char *buf = ( char* )buffer;
  size_t count = 0;
  while( count < size && IsOk() ) {
    while( m_parent_i_stream->IsOk() && m_tee->GetCount() == 0 ) {
      m_parent_i_stream->Read( m_dummy.data(), BUFSIZE );
    }
    size_t n = m_tee->GetData( buf + count, size - count );
    count += n;
    if( n == 0 && m_tee->Final() ) {
      m_lasterror = m_parent_i_stream->GetLastError();
    }
  }
  m_pos += count;
  return count;
}

class wxZlibOutputStream2 : public wxZlibOutputStream {
  public:
    wxZlibOutputStream2( wxOutputStream& stream, int level ) :
      wxZlibOutputStream( stream, level, wxZLIB_NO_HEADER ) { }
    bool Open( wxOutputStream& stream );
    bool Close() { DoFlush( true ); m_pos = wxInvalidOffset; return IsOk(); }
};

bool wxZlibOutputStream2::Open( wxOutputStream& stream ) {
  wxCHECK( m_pos == wxInvalidOffset, false );
  m_deflate->next_out = m_z_buffer;
  m_deflate->avail_out = m_z_size;
  m_pos = 0;
  m_lasterror = wxSTREAM_NO_ERROR;
  m_parent_o_stream = &stream;
  if( deflateReset( m_deflate ) != Z_OK ) {
    wxLogError( _( "can't re-initialize zlib deflate stream" ) );
    m_lasterror = wxSTREAM_WRITE_ERROR;
    return false;
  }
  return true;
}

class wxZlibInputStream2 : public wxZlibInputStream {
  public:
    wxZlibInputStream2( wxInputStream& stream ) :
      wxZlibInputStream( stream, wxZLIB_NO_HEADER ) { }
    bool Open( wxInputStream& stream );
};

bool wxZlibInputStream2::Open( wxInputStream& stream ) {
  m_inflate->avail_in = 0;
  m_pos = 0;
  m_lasterror = wxSTREAM_NO_ERROR;
  m_parent_i_stream = &stream;
  if( inflateReset( m_inflate ) != Z_OK ) {
    wxLogError( _( "can't re-initialize zlib inflate stream" ) );
    m_lasterror = wxSTREAM_READ_ERROR;
    return false;
  }
  return true;
}

class wxZipMemory {
  public:
    wxZipMemory() : m_data( NULL ), m_size( 0 ), m_capacity( 0 ), m_ref( 1 ) { }

    wxZipMemory *AddRef() { m_ref++; return this; }
    void Release() { if( --m_ref == 0 ) delete this; }

    char *GetData() const { return m_data; }
    size_t GetSize() const { return m_size; }
    size_t GetCapacity() const { return m_capacity; }

    wxZipMemory *Unique( size_t size );

  private:
    ~wxZipMemory() { delete [] m_data; }

    char *m_data;
    size_t m_size;
    size_t m_capacity;
    int m_ref;

    wxSUPPRESS_GCC_PRIVATE_DTOR_WARNING( wxZipMemory )
};

wxZipMemory *wxZipMemory::Unique( size_t size ) {
  wxZipMemory *zm;
  if( m_ref > 1 ) {
    --m_ref;
    zm = new wxZipMemory;
  } else
  { zm = this; }
  if( zm->m_capacity < size ) {
    delete [] zm->m_data;
    zm->m_data = new char[size];
    zm->m_capacity = size;
  }
  zm->m_size = size;
  return zm;
}

static inline wxZipMemory *AddRef( wxZipMemory *zm ) {
  if( zm ) {
    zm->AddRef();
  }
  return zm;
}

static inline void Release( wxZipMemory *zm ) {
  if( zm ) {
    zm->Release();
  }
}

static void Copy( wxZipMemory*& dest, wxZipMemory *src ) {
  Release( dest );
  dest = AddRef( src );
}

static void Unique( wxZipMemory*& zm, size_t size ) {
  if( !zm && size ) {
    zm = new wxZipMemory;
  }
  if( zm ) {
    zm = zm->Unique( size );
  }
}

WX_DECLARE_HASH_MAP( long, wxZipEntry*, wxIntegerHash,wxIntegerEqual, wxOffsetZipEntryMap_ );

class wxZipWeakLinks {
  public:
    wxZipWeakLinks() : m_ref( 1 ) { }

    void Release( const wxZipInputStream * ( x ) ) { if( --m_ref == 0 ) delete this; }
    void Release( wxFileOffset key ) { RemoveEntry( key ); if( --m_ref == 0 ) delete this; }

    wxZipWeakLinks *AddEntry( wxZipEntry *entry, wxFileOffset key );
    void RemoveEntry( wxFileOffset key ) { m_entries.erase( wx_truncate_cast( key_type, key ) ); }
    wxZipEntry *GetEntry( wxFileOffset key ) const;
    bool IsEmpty() const { return m_entries.empty(); }

  private:
    ~wxZipWeakLinks() { wxASSERT( IsEmpty() ); }

    typedef wxOffsetZipEntryMap_::key_type key_type;

    int m_ref;
    wxOffsetZipEntryMap_ m_entries;

    wxSUPPRESS_GCC_PRIVATE_DTOR_WARNING( wxZipWeakLinks )
};

wxZipWeakLinks *wxZipWeakLinks::AddEntry( wxZipEntry *entry, wxFileOffset key ) {
  m_entries[wx_truncate_cast( key_type, key )] = entry;
  m_ref++;
  return this;
}

wxZipEntry *wxZipWeakLinks::GetEntry( wxFileOffset key ) const {
  wxOffsetZipEntryMap_::const_iterator it =
    m_entries.find( wx_truncate_cast( key_type, key ) );
  return it != m_entries.end() ?  it->second : NULL;
}

wxZipEntry::wxZipEntry(const wxString& name,const wxDateTime& dt,wxFileOffset size )
  :
  m_SystemMadeBy( wxZIP_SYSTEM_MSDOS ),
  m_VersionMadeBy( 2 * 10 + 8 ),
  m_VersionNeeded( VERSION_NEEDED_TO_EXTRACT ),
  m_Flags( 0 ),
  m_Method( wxZIP_METHOD_DEFAULT ),
  m_DateTime( dt ),
  m_Crc( 0 ),
  m_CompressedSize( wxInvalidOffset ),
  m_Size( size ),
  m_Key( wxInvalidOffset ),
  m_Offset( wxInvalidOffset ),
  m_DiskStart( 0 ),
  m_InternalAttributes( 0 ),
  m_ExternalAttributes( 0 ),
  m_Extra( NULL ),
  m_LocalExtra( NULL ),
  m_zipnotifier( NULL ),
  m_backlink( NULL ) {
  if( !name.empty() ) {
    SetName( name );
  }
}

wxZipEntry::~wxZipEntry() {
  if( m_backlink ) {
    m_backlink->Release( m_Key );
  }
  Release( m_Extra );
  Release( m_LocalExtra );
}

wxZipEntry::wxZipEntry( const wxZipEntry& e )
  : wxArchiveEntry( e ),
    m_SystemMadeBy( e.m_SystemMadeBy ),
    m_VersionMadeBy( e.m_VersionMadeBy ),
    m_VersionNeeded( e.m_VersionNeeded ),
    m_Flags( e.m_Flags ),
    m_Method( e.m_Method ),
    m_DateTime( e.m_DateTime ),
    m_Crc( e.m_Crc ),
    m_CompressedSize( e.m_CompressedSize ),
    m_Size( e.m_Size ),
    m_Name( e.m_Name ),
    m_Key( e.m_Key ),
    m_Offset( e.m_Offset ),
    m_Comment( e.m_Comment ),
    m_DiskStart( e.m_DiskStart ),
    m_InternalAttributes( e.m_InternalAttributes ),
    m_ExternalAttributes( e.m_ExternalAttributes ),
    m_Extra( AddRef( e.m_Extra ) ),
    m_LocalExtra( AddRef( e.m_LocalExtra ) ),
    m_zipnotifier( NULL ),
    m_backlink( NULL ) {
}

wxZipEntry& wxZipEntry::operator=( const wxZipEntry& e ) {
  if( &e != this ) {
    m_SystemMadeBy = e.m_SystemMadeBy;
    m_VersionMadeBy = e.m_VersionMadeBy;
    m_VersionNeeded = e.m_VersionNeeded;
    m_Flags = e.m_Flags;
    m_Method = e.m_Method;
    m_DateTime = e.m_DateTime;
    m_Crc = e.m_Crc;
    m_CompressedSize = e.m_CompressedSize;
    m_Size = e.m_Size;
    m_Name = e.m_Name;
    m_Key = e.m_Key;
    m_Offset = e.m_Offset;
    m_Comment = e.m_Comment;
    m_DiskStart = e.m_DiskStart;
    m_InternalAttributes = e.m_InternalAttributes;
    m_ExternalAttributes = e.m_ExternalAttributes;
    Copy( m_Extra, e.m_Extra );
    Copy( m_LocalExtra, e.m_LocalExtra );
    m_zipnotifier = NULL;
    if( m_backlink ) {
      m_backlink->Release( m_Key );
      m_backlink = NULL;
    }
  }
  return *this;
}

wxString wxZipEntry::GetName( wxPathFormat format ) const {
  bool isDir = IsDir() && !m_Name.empty();
  switch( wxFileName::GetFormat( format ) ) {
    case wxPATH_DOS: {
      wxString name( isDir ? m_Name + _T( "\\" ) : m_Name );
      for( size_t i = 0; i < name.length(); i++ )
        if( name[i] == _T( '/' ) ) {
          name[i] = _T( '\\' );
        }
      return name;
    }
    case wxPATH_UNIX:
      return isDir ? m_Name + _T( "/" ) : m_Name;
    default:
      ;
  }
  wxFileName fn;
  if( isDir ) {
    fn.AssignDir( m_Name, wxPATH_UNIX );
  } else {
    fn.Assign( m_Name, wxPATH_UNIX );
  }
  return fn.GetFullPath( format );
}

wxString wxZipEntry::GetInternalName( const wxString& name, wxPathFormat format, bool *pIsDir ) {
  wxString internal;
  if( wxFileName::GetFormat( format ) != wxPATH_UNIX ) {
    internal = wxFileName( name, format ).GetFullPath( wxPATH_UNIX );
  } else {
    internal = name;
  }
  bool isDir = !internal.empty() && internal.Last() == '/';
  if( pIsDir ) {
    *pIsDir = isDir;
  }
  if( isDir ) {
    internal.erase( internal.length() - 1 );
  }
  while( !internal.empty() && *internal.begin() == '/' ) {
    internal.erase( 0, 1 );
  }
  while( !internal.empty() && internal.compare( 0, 2, _T( "./" ) ) == 0 ) {
    internal.erase( 0, 2 );
  }
  if( internal == _T( "." ) || internal == _T( ".." ) ) {
    internal = wxEmptyString;
  }
  return internal;
}

void wxZipEntry::SetSystemMadeBy( int system ) {
  int mode = GetMode();
  bool wasUnix = IsMadeByUnix();
  m_SystemMadeBy = ( wxUint8 )system;
  if( !wasUnix && IsMadeByUnix() ) {
    SetIsDir( IsDir() );
    SetMode( mode );
  } else if( wasUnix && !IsMadeByUnix() ) {
    m_ExternalAttributes &= 0xffff;
  }
}

void wxZipEntry::SetIsDir( bool isDir ) {
  if( isDir ) {
    m_ExternalAttributes |= wxZIP_A_SUBDIR;
  } else {
    m_ExternalAttributes &= ~wxZIP_A_SUBDIR;
  }
  if( IsMadeByUnix() ) {
    m_ExternalAttributes &= ~wxZIP_S_IFMT;
    if( isDir ) {
      m_ExternalAttributes |= wxZIP_S_IFDIR;
    } else{ m_ExternalAttributes |= wxZIP_S_IFREG; }
  }
}

int wxZipEntry::GetMode() const {
  if( IsMadeByUnix() ) {
    return ( m_ExternalAttributes >> 16 ) & 0777;
  }
  int mode = 0644;
  if( m_ExternalAttributes & wxZIP_A_RDONLY ) {
    mode &= ~0200;
  }
  if( m_ExternalAttributes & wxZIP_A_SUBDIR ) {
    mode |= 0111;
  }
  return mode;
}

void wxZipEntry::SetMode( int mode ) {
  if( mode & 0222 ) {
    m_ExternalAttributes &= ~wxZIP_A_RDONLY;
  } else {
    m_ExternalAttributes |= wxZIP_A_RDONLY;
  }
  if( IsMadeByUnix() ) {
    m_ExternalAttributes &= ~( 0777L << 16 );
    m_ExternalAttributes |= ( mode & 0777L ) << 16;
  }
}

const char *wxZipEntry::GetExtra() const {
  return m_Extra ? m_Extra->GetData() : NULL;
}

size_t wxZipEntry::GetExtraLen() const {
  return m_Extra ? m_Extra->GetSize() : 0;
}

void wxZipEntry::SetExtra( const char *extra, size_t len ) {
  Unique( m_Extra, len );
  if( len ) {
    memcpy( m_Extra->GetData(), extra, len );
  }
}

const char *wxZipEntry::GetLocalExtra() const {
  return m_LocalExtra ? m_LocalExtra->GetData() : NULL;
}

size_t  wxZipEntry::GetLocalExtraLen() const {
  return m_LocalExtra ? m_LocalExtra->GetSize() : 0;
}

void wxZipEntry::SetLocalExtra( const char *extra, size_t len ) {
  Unique( m_LocalExtra, len );
  if( len ) {
    memcpy( m_LocalExtra->GetData(), extra, len );
  }
}

void wxZipEntry::SetNotifier( wxZipNotifier& notifier ) {
  wxArchiveEntry::UnsetNotifier();
  m_zipnotifier = &notifier;
  m_zipnotifier->OnEntryUpdated( *this );
}

void wxZipEntry::Notify() {
  if( m_zipnotifier ) {
    m_zipnotifier->OnEntryUpdated( *this );
  } else if( GetNotifier() ) {
    GetNotifier()->OnEntryUpdated( *this );
  }
}

void wxZipEntry::UnsetNotifier() {
  wxArchiveEntry::UnsetNotifier();
  m_zipnotifier = NULL;
}

size_t wxZipEntry::ReadLocal( wxInputStream& stream, wxMBConv& conv ) {
  wxUint16 nameLen, extraLen;
  wxUint32 compressedSize, size, crc;
  wxZipHeader ds( stream, LOCAL_SIZE - 4 );
  if( !ds ) {
    return 0;
  }
  ds >> m_VersionNeeded >> m_Flags >> m_Method;
  SetDateTime( wxDateTime().SetFromDOS( ds.Read32() ) );
  ds >> crc >> compressedSize >> size >> nameLen >> extraLen;
  bool sumsValid = ( m_Flags & wxZIP_SUMS_FOLLOW ) == 0;
  if( sumsValid || crc ) {
    m_Crc = crc;
  }
  if( ( sumsValid || compressedSize ) || m_Method == wxZIP_METHOD_STORE ) {
    m_CompressedSize = compressedSize;
  }
  if( ( sumsValid || size ) || m_Method == wxZIP_METHOD_STORE ) {
    m_Size = size;
  }
  SetName( ReadString( stream, nameLen, conv ), wxPATH_UNIX );
  if( stream.LastRead() != nameLen + 0u ) {
    return 0;
  }
  if( extraLen || GetLocalExtraLen() ) {
    Unique( m_LocalExtra, extraLen );
    if( extraLen ) {
      stream.Read( m_LocalExtra->GetData(), extraLen );
      if( stream.LastRead() != extraLen + 0u ) {
        return 0;
      }
    }
  }
  return LOCAL_SIZE + nameLen + extraLen;
}

size_t wxZipEntry::WriteLocal( wxOutputStream& stream, wxMBConv& conv ) const {
  wxString unixName = GetName( wxPATH_UNIX );
  const wxWX2MBbuf name_buf = conv.cWX2MB( unixName );
  const char *name = name_buf;
  if( !name ) {
    name = "";
  }
  wxUint16 nameLen = wx_truncate_cast( wxUint16, strlen( name ) );
  wxDataOutputStream ds( stream );
  ds << m_VersionNeeded << m_Flags << m_Method;
  ds.Write32( GetDateTime().GetAsDOS() );
  ds.Write32( m_Crc );
  ds.Write32( m_CompressedSize != wxInvalidOffset ?
              wx_truncate_cast( wxUint32, m_CompressedSize ) : 0 );
  ds.Write32( m_Size != wxInvalidOffset ?
              wx_truncate_cast( wxUint32, m_Size ) : 0 );
  ds << nameLen;
  wxUint16 extraLen = wx_truncate_cast( wxUint16, GetLocalExtraLen() );
  ds.Write16( extraLen );
  stream.Write( name, nameLen );
  if( extraLen ) {
    stream.Write( m_LocalExtra->GetData(), extraLen );
  }
  return LOCAL_SIZE + nameLen + extraLen;
}

size_t wxZipEntry::ReadCentral( wxInputStream& stream, wxMBConv& conv ) {
  wxUint16 nameLen, extraLen, commentLen;
  wxZipHeader ds( stream, CENTRAL_SIZE - 4 );
  if( !ds ) {
    return 0;
  }
  ds >> m_VersionMadeBy >> m_SystemMadeBy;
  SetVersionNeeded( ds.Read16() );
  SetFlags( ds.Read16() );
  SetMethod( ds.Read16() );
  SetDateTime( wxDateTime().SetFromDOS( ds.Read32() ) );
  SetCrc( ds.Read32() );
  SetCompressedSize( ds.Read32() );
  SetSize( ds.Read32() );
  ds >> nameLen >> extraLen >> commentLen
     >> m_DiskStart >> m_InternalAttributes >> m_ExternalAttributes;
  SetOffset( ds.Read32() );
  SetName( ReadString( stream, nameLen, conv ), wxPATH_UNIX );
  if( stream.LastRead() != nameLen + 0u ) {
    return 0;
  }
  if( extraLen || GetExtraLen() ) {
    Unique( m_Extra, extraLen );
    if( extraLen ) {
      stream.Read( m_Extra->GetData(), extraLen );
      if( stream.LastRead() != extraLen + 0u ) {
        return 0;
      }
    }
  }
  if( commentLen ) {
    m_Comment = ReadString( stream, commentLen, conv );
    if( stream.LastRead() != commentLen + 0u ) {
      return 0;
    }
  } else
  { m_Comment.clear(); }
  return CENTRAL_SIZE + nameLen + extraLen + commentLen;
}

size_t wxZipEntry::WriteCentral( wxOutputStream& stream, wxMBConv& conv ) const {
  wxString unixName = GetName( wxPATH_UNIX );
  const wxWX2MBbuf name_buf = conv.cWX2MB( unixName );
  const char *name = name_buf;
  if( !name ) {
    name = "";
  }
  wxUint16 nameLen = wx_truncate_cast( wxUint16, strlen( name ) );
  const wxWX2MBbuf comment_buf = conv.cWX2MB( m_Comment );
  const char *comment = comment_buf;
  if( !comment ) {
    comment = "";
  }
  wxUint16 commentLen = wx_truncate_cast( wxUint16, strlen( comment ) );
  wxUint16 extraLen = wx_truncate_cast( wxUint16, GetExtraLen() );
  wxDataOutputStream ds( stream );
  ds << CENTRAL_MAGIC << m_VersionMadeBy << m_SystemMadeBy;
  ds.Write16( wx_truncate_cast( wxUint16, GetVersionNeeded() ) );
  ds.Write16( wx_truncate_cast( wxUint16, GetFlags() ) );
  ds.Write16( wx_truncate_cast( wxUint16, GetMethod() ) );
  ds.Write32( GetDateTime().GetAsDOS() );
  ds.Write32( GetCrc() );
  ds.Write32( wx_truncate_cast( wxUint32, GetCompressedSize() ) );
  ds.Write32( wx_truncate_cast( wxUint32, GetSize() ) );
  ds.Write16( nameLen );
  ds.Write16( extraLen );
  ds << commentLen << m_DiskStart << m_InternalAttributes
     << m_ExternalAttributes << wx_truncate_cast( wxUint32, GetOffset() );
  stream.Write( name, nameLen );
  if( extraLen ) {
    stream.Write( GetExtra(), extraLen );
  }
  stream.Write( comment, commentLen );
  return CENTRAL_SIZE + nameLen + extraLen + commentLen;
}

// Info-zip prefixes this record with a signature, but pkzip doesn't. So if
// the 1st value is the signature then it is probably an info-zip record,
// though there is a small chance that it is in fact a pkzip record which
// happens to have the signature as it's CRC.
//
size_t wxZipEntry::ReadDescriptor( wxInputStream& stream ) {
  wxZipHeader ds( stream, SUMS_SIZE );
  if( !ds ) {
    return 0;
  }
  m_Crc = ds.Read32();
  m_CompressedSize = ds.Read32();
  m_Size = ds.Read32();
  // if 1st value is the signature then this is probably an info-zip record
  if( m_Crc == SUMS_MAGIC ) {
    wxZipHeader buf( stream, 8 );
    wxUint32 u1 = buf.GetSize() >= 4 ? buf.Read32() : ( wxUint32 )LOCAL_MAGIC;
    wxUint32 u2 = buf.GetSize() == 8 ? buf.Read32() : 0;
    // look for the signature of the following record to decide which
    if( ( u1 == LOCAL_MAGIC || u1 == CENTRAL_MAGIC ) &&
        ( u2 != LOCAL_MAGIC && u2 != CENTRAL_MAGIC ) ) {
      // it's a pkzip style record after all!
      if( buf.GetSize() > 0 ) {
        stream.Ungetch( buf.GetData(), buf.GetSize() );
      }
    } else {
      // it's an info-zip record as expected
      if( buf.GetSize() > 4 ) {
        stream.Ungetch( buf.GetData() + 4, buf.GetSize() - 4 );
      }
      m_Crc = wx_truncate_cast( wxUint32, m_CompressedSize );
      m_CompressedSize = m_Size;
      m_Size = u1;
      return SUMS_SIZE + 4;
    }
  }
  return SUMS_SIZE;
}

size_t wxZipEntry::WriteDescriptor( wxOutputStream& stream, wxUint32 crc,
                                    wxFileOffset compressedSize, wxFileOffset size ) {
  m_Crc = crc;
  m_CompressedSize = compressedSize;
  m_Size = size;
  wxDataOutputStream ds( stream );
  ds.Write32( crc );
  ds.Write32( wx_truncate_cast( wxUint32, compressedSize ) );
  ds.Write32( wx_truncate_cast( wxUint32, size ) );
  return SUMS_SIZE;
}


/////////////////////////////////////////////////////////////////////////////
// wxZipEndRec - holds the end of central directory record

class wxZipEndRec {
  public:
    wxZipEndRec();

    int GetDiskNumber() const                   { return m_DiskNumber; }
    int GetStartDisk() const                    { return m_StartDisk; }
    int GetEntriesHere() const                  { return m_EntriesHere; }
    int GetTotalEntries() const                 { return m_TotalEntries; }
    wxFileOffset GetSize() const                { return m_Size; }
    wxFileOffset GetOffset() const              { return m_Offset; }
    wxString GetComment() const                 { return m_Comment; }

    void SetDiskNumber( int num ) { m_DiskNumber = wx_truncate_cast( wxUint16, num ); }
    void SetStartDisk( int num ) { m_StartDisk = wx_truncate_cast( wxUint16, num ); }
    void SetEntriesHere( int num ) { m_EntriesHere = wx_truncate_cast( wxUint16, num ); }
    void SetTotalEntries( int num ) { m_TotalEntries = wx_truncate_cast( wxUint16, num ); }
    void SetSize( wxFileOffset size ) { m_Size = wx_truncate_cast( wxUint32, size ); }
    void SetOffset( wxFileOffset offset ) { m_Offset = wx_truncate_cast( wxUint32, offset ); }
    void SetComment( const wxString& comment ) { m_Comment = comment; }

    bool Read( wxInputStream& stream, wxMBConv& conv );
    bool Write( wxOutputStream& stream, wxMBConv& conv ) const;

  private:
    wxUint16 m_DiskNumber;
    wxUint16 m_StartDisk;
    wxUint16 m_EntriesHere;
    wxUint16 m_TotalEntries;
    wxUint32 m_Size;
    wxUint32 m_Offset;
    wxString m_Comment;
};

wxZipEndRec::wxZipEndRec()
  : m_DiskNumber( 0 ),
    m_StartDisk( 0 ),
    m_EntriesHere( 0 ),
    m_TotalEntries( 0 ),
    m_Size( 0 ),
    m_Offset( 0 ) {
}

bool wxZipEndRec::Write( wxOutputStream& stream, wxMBConv& conv ) const {
  const wxWX2MBbuf comment_buf = conv.cWX2MB( m_Comment );
  const char *comment = comment_buf;
  if( !comment ) {
    comment = "";
  }
  wxUint16 commentLen = ( wxUint16 )strlen( comment );
  wxDataOutputStream ds( stream );
  ds << END_MAGIC << m_DiskNumber << m_StartDisk << m_EntriesHere
     << m_TotalEntries << m_Size << m_Offset << commentLen;
  stream.Write( comment, commentLen );
  return stream.IsOk();
}

bool wxZipEndRec::Read( wxInputStream& stream, wxMBConv& conv ) {
  wxZipHeader ds( stream, END_SIZE - 4 );
  if( !ds ) {
    return false;
  }
  wxUint16 commentLen;
  ds >> m_DiskNumber >> m_StartDisk >> m_EntriesHere
     >> m_TotalEntries >> m_Size >> m_Offset >> commentLen;
  if( commentLen ) {
    m_Comment = ReadString( stream, commentLen, conv );
    if( stream.LastRead() != commentLen + 0u ) {
      return false;
    }
  }
  if( m_DiskNumber != 0 || m_StartDisk != 0 ||
      m_EntriesHere != m_TotalEntries ) {
    wxLogWarning( _( "assuming this is a multi-part zip concatenated" ) );
  }
  return true;
}


/////////////////////////////////////////////////////////////////////////////
// A weak link from an input stream to an output stream

class wxZipStreamLink {
  public:
    wxZipStreamLink( wxZipOutputStream *stream ) : m_ref( 1 ), m_stream( stream ) { }

    wxZipStreamLink *AddRef() { m_ref++; return this; }
    wxZipOutputStream *GetOutputStream() const { return m_stream; }

    void Release( class wxZipInputStream * ( s ) ) { if( --m_ref == 0 ) delete this; }
    void Release( class wxZipOutputStream * ( s ) ) { m_stream = NULL; if( --m_ref == 0 ) delete this; }

  private:
    ~wxZipStreamLink() { }

    int m_ref;
    wxZipOutputStream *m_stream;

    wxSUPPRESS_GCC_PRIVATE_DTOR_WARNING( wxZipStreamLink )
};


/////////////////////////////////////////////////////////////////////////////
// Input stream

// leave the default wxZipEntryPtr free for users
wxDECLARE_SCOPED_PTR( wxZipEntry, wxZipEntryPtr_ )
wxDEFINE_SCOPED_PTR( wxZipEntry, wxZipEntryPtr_ )

// constructor
//
wxZipInputStream::wxZipInputStream( wxInputStream& stream,
                                    wxMBConv& conv /*=wxConvLocal*/ )
  : wxArchiveInputStream( stream, conv ) {
  Init();
}

wxZipInputStream::wxZipInputStream( wxInputStream *stream,
                                    wxMBConv& conv /*=wxConvLocal*/ )
  : wxArchiveInputStream( stream, conv ) {
  Init();
}

void wxZipInputStream::Init( const wxString& file ) {
  // no error messages
  wxLogNull nolog;
  Init();
  m_allowSeeking = true;
  wxFFileInputStream *ffile;
  ffile = wx_static_cast( wxFFileInputStream*, m_parent_i_stream );
  wxZipEntryPtr_ entry;
  if( ffile->Ok() ) {
    do {
      entry.reset( GetNextEntry() );
    } while( entry.get() != NULL && entry->GetInternalName() != file );
  }
  if( entry.get() == NULL ) {
    m_lasterror = wxSTREAM_READ_ERROR;
  }
}

wxInputStream* wxZipInputStream::OpenFile( const wxString& archive ) {
  wxLogNull nolog;
  return new wxFFileInputStream( archive );
}

void wxZipInputStream::Init() {
  m_store = new wxStoredInputStream( *m_parent_i_stream );
  m_inflate = NULL;
  m_rawin = NULL;
  m_raw = false;
  m_headerSize = 0;
  m_decomp = NULL;
  m_parentSeekable = false;
  m_weaklinks = new wxZipWeakLinks;
  m_streamlink = NULL;
  m_offsetAdjustment = 0;
  m_position = wxInvalidOffset;
  m_signature = 0;
  m_TotalEntries = 0;
  m_lasterror = m_parent_i_stream->GetLastError();
  m_allowSeeking = false;
}

wxZipInputStream::~wxZipInputStream() {
  CloseDecompressor( m_decomp );
  delete m_store;
  delete m_inflate;
  delete m_rawin;
  m_weaklinks->Release( this );
  if( m_streamlink ) {
    m_streamlink->Release( this );
  }
}

wxString wxZipInputStream::GetComment() {
  if( m_position == wxInvalidOffset )
    if( !LoadEndRecord() ) {
      return wxEmptyString;
    }
  if( !m_parentSeekable && Eof() && m_signature ) {
    m_lasterror = wxSTREAM_NO_ERROR;
    m_lasterror = ReadLocal( true );
  }
  return m_Comment;
}

int wxZipInputStream::GetTotalEntries() {
  if( m_position == wxInvalidOffset ) {
    LoadEndRecord();
  }
  return m_TotalEntries;
}

wxZipStreamLink *wxZipInputStream::MakeLink( wxZipOutputStream *out ) {
  wxZipStreamLink *link = NULL;
  if( !m_parentSeekable && ( IsOpened() || !Eof() ) ) {
    link = new wxZipStreamLink( out );
    if( m_streamlink ) {
      m_streamlink->Release( this );
    }
    m_streamlink = link->AddRef();
  }
  return link;
}

bool wxZipInputStream::LoadEndRecord() {
  wxCHECK( m_position == wxInvalidOffset, false );
  if( !IsOk() ) {
    return false;
  }
  m_position = 0;
  // First find the end-of-central-directory record.
  if( !FindEndRecord() ) {
    // failed, so either this is a non-seekable stream (ok), or not a zip
    if( m_parentSeekable ) {
      m_lasterror = wxSTREAM_READ_ERROR;
      wxLogError( _( "invalid zip file" ) );
      return false;
    } else {
      wxLogNull nolog;
      wxFileOffset pos = m_parent_i_stream->TellI();
      if( pos != wxInvalidOffset ) {
        m_offsetAdjustment = m_position = pos;
      }
      return true;
    }
  }
  wxZipEndRec endrec;
  // Read in the end record
  wxFileOffset endPos = m_parent_i_stream->TellI() - 4;
  if( !endrec.Read( *m_parent_i_stream, GetConv() ) ) {
    return false;
  }
  m_TotalEntries = endrec.GetTotalEntries();
  m_Comment = endrec.GetComment();
  // Now find the central-directory. we have the file offset of
  // the CD, so look there first.
  if( m_parent_i_stream->SeekI( endrec.GetOffset() ) != wxInvalidOffset &&
      ReadSignature() == CENTRAL_MAGIC ) {
    m_signature = CENTRAL_MAGIC;
    m_position = endrec.GetOffset();
    m_offsetAdjustment = 0;
    return true;
  }
  // If it's not there, then it could be that the zip has been appended
  // to a self extractor, so take the CD size (also in endrec), subtract
  // it from the file offset of the end-central-directory and look there.
  if( m_parent_i_stream->SeekI( endPos - endrec.GetSize() )
      != wxInvalidOffset && ReadSignature() == CENTRAL_MAGIC ) {
    m_signature = CENTRAL_MAGIC;
    m_position = endPos - endrec.GetSize();
    m_offsetAdjustment = m_position - endrec.GetOffset();
    return true;
  }
  wxLogError( _( "can't find central directory in zip" ) );
  m_lasterror = wxSTREAM_READ_ERROR;
  return false;
}

// Find the end-of-central-directory record.
// If found the stream will be positioned just past the 4 signature bytes.
//
bool wxZipInputStream::FindEndRecord() {
  if( !m_parent_i_stream->IsSeekable() ) {
    return false;
  }
  // usually it's 22 bytes in size and the last thing in the file
  {
    wxLogNull nolog;
    if( m_parent_i_stream->SeekI( -END_SIZE, wxFromEnd ) == wxInvalidOffset ) {
      return false;
    }
  }
  m_parentSeekable = true;
  m_signature = 0;
  char magic[4];
  if( m_parent_i_stream->Read( magic, 4 ).LastRead() != 4 ) {
    return false;
  }
  if( ( m_signature = CrackUint32( magic ) ) == END_MAGIC ) {
    return true;
  }
  // unfortunately, the record has a comment field that can be up to 65535
  // bytes in length, so if the signature not found then search backwards.
  wxFileOffset pos = m_parent_i_stream->TellI();
  const int BUFSIZE = 1024;
  wxCharBuffer buf( BUFSIZE );
  memcpy( buf.data(), magic, 3 );
  wxFileOffset minpos = wxMax( pos - 65535L, 0 );
  while( pos > minpos ) {
    size_t len = wx_truncate_cast( size_t,
                                   pos - wxMax( pos - ( BUFSIZE - 3 ), minpos ) );
    memcpy( buf.data() + len, buf, 3 );
    pos -= len;
    if( m_parent_i_stream->SeekI( pos, wxFromStart ) == wxInvalidOffset ||
        m_parent_i_stream->Read( buf.data(), len ).LastRead() != len ) {
      return false;
    }
    char *p = buf.data() + len;
    while( p-- > buf.data() ) {
      if( ( m_signature = CrackUint32( p ) ) == END_MAGIC ) {
        size_t remainder = buf.data() + len - p;
        if( remainder > 4 ) {
          m_parent_i_stream->Ungetch( p + 4, remainder - 4 );
        }
        return true;
      }
    }
  }
  return false;
}

wxZipEntry *wxZipInputStream::GetNextEntry() {
  if( m_position == wxInvalidOffset )
    if( !LoadEndRecord() ) {
      return NULL;
    }
  m_lasterror = m_parentSeekable ? ReadCentral() : ReadLocal();
  if( !IsOk() ) {
    return NULL;
  }
  wxZipEntryPtr_ entry( new wxZipEntry( m_entry ) );
  entry->m_backlink = m_weaklinks->AddEntry( entry.get(), entry->GetKey() );
  return entry.release();
}

wxStreamError wxZipInputStream::ReadCentral() {
  if( !AtHeader() ) {
    CloseEntry();
  }
  if( m_signature == END_MAGIC ) {
    return wxSTREAM_EOF;
  }
  if( m_signature != CENTRAL_MAGIC ) {
    wxLogError( _( "error reading zip central directory" ) );
    return wxSTREAM_READ_ERROR;
  }
  if( QuietSeek( *m_parent_i_stream, m_position + 4 ) == wxInvalidOffset ) {
    return wxSTREAM_READ_ERROR;
  }
  size_t size = m_entry.ReadCentral( *m_parent_i_stream, GetConv() );
  if( !size ) {
    m_signature = 0;
    return wxSTREAM_READ_ERROR;
  }
  m_position += size;
  m_signature = ReadSignature();
  if( m_offsetAdjustment ) {
    m_entry.SetOffset( m_entry.GetOffset() + m_offsetAdjustment );
  }
  m_entry.SetKey( m_entry.GetOffset() );
  return wxSTREAM_NO_ERROR;
}

wxStreamError wxZipInputStream::ReadLocal( bool readEndRec /*=false*/ ) {
  if( !AtHeader() ) {
    CloseEntry();
  }
  if( !m_signature ) {
    m_signature = ReadSignature();
  }
  if( m_signature == CENTRAL_MAGIC || m_signature == END_MAGIC ) {
    if( m_streamlink && !m_streamlink->GetOutputStream() ) {
      m_streamlink->Release( this );
      m_streamlink = NULL;
    }
  }
  while( m_signature == CENTRAL_MAGIC ) {
    if( m_weaklinks->IsEmpty() && m_streamlink == NULL ) {
      return wxSTREAM_EOF;
    }
    size_t size = m_entry.ReadCentral( *m_parent_i_stream, GetConv() );
    m_position += size;
    m_signature = 0;
    if( !size ) {
      return wxSTREAM_READ_ERROR;
    }
    wxZipEntry *entry = m_weaklinks->GetEntry( m_entry.GetOffset() );
    if( entry ) {
      entry->SetSystemMadeBy( m_entry.GetSystemMadeBy() );
      entry->SetVersionMadeBy( m_entry.GetVersionMadeBy() );
      entry->SetComment( m_entry.GetComment() );
      entry->SetDiskStart( m_entry.GetDiskStart() );
      entry->SetInternalAttributes( m_entry.GetInternalAttributes() );
      entry->SetExternalAttributes( m_entry.GetExternalAttributes() );
      Copy( entry->m_Extra, m_entry.m_Extra );
      entry->Notify();
      m_weaklinks->RemoveEntry( entry->GetOffset() );
    }
    m_signature = ReadSignature();
  }
  if( m_signature == END_MAGIC ) {
    if( readEndRec || m_streamlink ) {
      wxZipEndRec endrec;
      endrec.Read( *m_parent_i_stream, GetConv() );
      m_Comment = endrec.GetComment();
      m_signature = 0;
      if( m_streamlink ) {
        m_streamlink->GetOutputStream()->SetComment( endrec.GetComment() );
        m_streamlink->Release( this );
        m_streamlink = NULL;
      }
    }
    return wxSTREAM_EOF;
  }
  if( m_signature == LOCAL_MAGIC ) {
    m_headerSize = m_entry.ReadLocal( *m_parent_i_stream, GetConv() );
    m_signature = 0;
    m_entry.SetOffset( m_position );
    m_entry.SetKey( m_position );
    if( m_headerSize ) {
      m_TotalEntries++;
      return wxSTREAM_NO_ERROR;
    }
  }
  wxLogError( _( "error reading zip local header" ) );
  return wxSTREAM_READ_ERROR;
}

wxUint32 wxZipInputStream::ReadSignature() {
  char magic[4];
  m_parent_i_stream->Read( magic, 4 );
  return m_parent_i_stream->LastRead() == 4 ? CrackUint32( magic ) : 0;
}

bool wxZipInputStream::OpenEntry( wxArchiveEntry& entry ) {
  wxZipEntry *zipEntry = wxStaticCast( &entry, wxZipEntry );
  return zipEntry ? OpenEntry( *zipEntry ) : false;
}

// Open an entry
//
bool wxZipInputStream::DoOpen( wxZipEntry *entry, bool raw ) {
  if( m_position == wxInvalidOffset )
    if( !LoadEndRecord() ) {
      return false;
    }
  if( m_lasterror == wxSTREAM_READ_ERROR ) {
    return false;
  }
  if( IsOpened() ) {
    CloseEntry();
  }
  m_raw = raw;
  if( entry ) {
    if( AfterHeader() && entry->GetKey() == m_entry.GetOffset() ) {
      return true;
    }
    // can only open the current entry on a non-seekable stream
    wxCHECK( m_parentSeekable, false );
  }
  m_lasterror = wxSTREAM_READ_ERROR;
  if( entry ) {
    m_entry = *entry;
  }
  if( m_parentSeekable ) {
    if( QuietSeek( *m_parent_i_stream, m_entry.GetOffset() )
        == wxInvalidOffset ) {
      return false;
    }
    if( ReadSignature() != LOCAL_MAGIC ) {
      wxLogError( _( "bad zipfile offset to entry" ) );
      return false;
    }
  }
  if( m_parentSeekable || AtHeader() ) {
    m_headerSize = m_entry.ReadLocal( *m_parent_i_stream, GetConv() );
    if( m_headerSize && m_parentSeekable ) {
      wxZipEntry *ref = m_weaklinks->GetEntry( m_entry.GetKey() );
      if( ref ) {
        Copy( ref->m_LocalExtra, m_entry.m_LocalExtra );
        ref->Notify();
        m_weaklinks->RemoveEntry( ref->GetKey() );
      }
      if( entry && entry != ref ) {
        Copy( entry->m_LocalExtra, m_entry.m_LocalExtra );
        entry->Notify();
      }
    }
  }
  if( m_headerSize ) {
    m_lasterror = wxSTREAM_NO_ERROR;
  }
  return IsOk();
}

bool wxZipInputStream::OpenDecompressor( bool raw /*=false*/ ) {
  wxASSERT( AfterHeader() );
  wxFileOffset compressedSize = m_entry.GetCompressedSize();
  if( raw ) {
    m_raw = true;
  }
  if( m_raw ) {
    if( compressedSize != wxInvalidOffset ) {
      m_store->Open( compressedSize );
      m_decomp = m_store;
    } else {
      if( !m_rawin ) {
        m_rawin = new wxRawInputStream( *m_parent_i_stream );
      }
      m_decomp = m_rawin->Open( OpenDecompressor( m_rawin->GetTee() ) );
    }
  } else {
    if( compressedSize != wxInvalidOffset &&
        ( m_entry.GetMethod() != wxZIP_METHOD_DEFLATE ||
          wxZlibInputStream::CanHandleGZip() ) ) {
      m_store->Open( compressedSize );
      m_decomp = OpenDecompressor( *m_store );
    } else
    { m_decomp = OpenDecompressor( *m_parent_i_stream ); }
  }
  m_crcAccumulator = crc32( 0, Z_NULL, 0 );
  m_lasterror = m_decomp ? m_decomp->GetLastError() : wxSTREAM_READ_ERROR;
  return IsOk();
}

// Can be overriden to add support for additional decompression methods
//
wxInputStream *wxZipInputStream::OpenDecompressor( wxInputStream& stream ) {
  switch( m_entry.GetMethod() ) {
    case wxZIP_METHOD_STORE:
      if( m_entry.GetSize() == wxInvalidOffset ) {
        wxLogError( _( "stored file length not in Zip header" ) );
        break;
      }
      m_store->Open( m_entry.GetSize() );
      return m_store;
    case wxZIP_METHOD_DEFLATE:
      if( !m_inflate ) {
        m_inflate = new wxZlibInputStream2( stream );
      } else
      { m_inflate->Open( stream ); }
      return m_inflate;
    default:
      wxLogError( _( "unsupported Zip compression method" ) );
  }
  return NULL;
}

bool wxZipInputStream::CloseDecompressor( wxInputStream *decomp ) {
  if( decomp && decomp == m_rawin ) {
    return CloseDecompressor( m_rawin->GetFilterInputStream() );
  }
  if( decomp != m_store && decomp != m_inflate ) {
    delete decomp;
  }
  return true;
}

// Closes the current entry and positions the underlying stream at the start
// of the next entry
//
bool wxZipInputStream::CloseEntry() {
  if( AtHeader() ) {
    return true;
  }
  if( m_lasterror == wxSTREAM_READ_ERROR ) {
    return false;
  }
  if( !m_parentSeekable ) {
    if( !IsOpened() && !OpenDecompressor( true ) ) {
      return false;
    }
    const int BUFSIZE = 8192;
    wxCharBuffer buf( BUFSIZE );
    while( IsOk() ) {
      Read( buf.data(), BUFSIZE );
    }
    m_position += m_headerSize + m_entry.GetCompressedSize();
  }
  if( m_lasterror == wxSTREAM_EOF ) {
    m_lasterror = wxSTREAM_NO_ERROR;
  }
  CloseDecompressor( m_decomp );
  m_decomp = NULL;
  m_entry = wxZipEntry();
  m_headerSize = 0;
  m_raw = false;
  return IsOk();
}

size_t wxZipInputStream::OnSysRead( void *buffer, size_t size ) {
  if( !IsOpened() )
    if( ( AtHeader() && !DoOpen() ) || !OpenDecompressor() ) {
      m_lasterror = wxSTREAM_READ_ERROR;
    }
  if( !IsOk() || !size ) {
    return 0;
  }
  size_t count = m_decomp->Read( buffer, size ).LastRead();
  if( !m_raw ) {
    m_crcAccumulator = crc32( m_crcAccumulator, ( Byte* )buffer, count );
  }
  if( count < size ) {
    m_lasterror = m_decomp->GetLastError();
  }
  if( Eof() ) {
    if( ( m_entry.GetFlags() & wxZIP_SUMS_FOLLOW ) != 0 ) {
      m_headerSize += m_entry.ReadDescriptor( *m_parent_i_stream );
      wxZipEntry *entry = m_weaklinks->GetEntry( m_entry.GetKey() );
      if( entry ) {
        entry->SetCrc( m_entry.GetCrc() );
        entry->SetCompressedSize( m_entry.GetCompressedSize() );
        entry->SetSize( m_entry.GetSize() );
        entry->Notify();
      }
    }
    if( !m_raw ) {
      m_lasterror = wxSTREAM_READ_ERROR;
      if( m_entry.GetSize() != TellI() )
        wxLogError( _( "reading zip stream (entry %s): bad length" ),
                    m_entry.GetName().c_str() );
      else if( m_crcAccumulator != m_entry.GetCrc() )
        wxLogError( _( "reading zip stream (entry %s): bad crc" ),
                    m_entry.GetName().c_str() );
      else
      { m_lasterror = wxSTREAM_EOF; }
    }
  }
  return count;
}

wxFileOffset wxZipInputStream::OnSysSeek( wxFileOffset seek, wxSeekMode mode ) {
  // seeking works when the stream is created with the compatibility
  // constructor
  if( !m_allowSeeking ) {
    return wxInvalidOffset;
  }
  if( !IsOpened() )
    if( ( AtHeader() && !DoOpen() ) || !OpenDecompressor() ) {
      m_lasterror = wxSTREAM_READ_ERROR;
    }
  if( !IsOk() ) {
    return wxInvalidOffset;
  }
  wxFileOffset nextpos;
  wxFileOffset pos = TellI();
  switch( mode ) {
    case wxFromCurrent :
      nextpos = seek + pos;
      break;
    case wxFromStart :
      nextpos = seek;
      break;
    case wxFromEnd :
      nextpos = GetLength() + seek;
      break;
    default :
      nextpos = pos;
      break; /* just to fool compiler, never happens */
  }
  wxFileOffset toskip wxDUMMY_INITIALIZE( 0 );
  if( nextpos >= pos ) {
    toskip = nextpos - pos;
  } else {
    wxZipEntry current( m_entry );
    if( !OpenEntry( current ) ) {
      m_lasterror = wxSTREAM_READ_ERROR;
      return pos;
    }
    toskip = nextpos;
  }
  if( toskip > 0 ) {
    const int BUFSIZE = 4096;
    size_t sz;
    char buffer[BUFSIZE];
    while( toskip > 0 ) {
      sz = wx_truncate_cast( size_t, wxMin( toskip, BUFSIZE ) );
      Read( buffer, sz );
      toskip -= sz;
    }
  }
  pos = nextpos;
  return pos;
}


/////////////////////////////////////////////////////////////////////////////
// Output stream

#include "wx/listimpl.cpp"
WX_DEFINE_LIST( wxZipEntryList_ )

wxZipOutputStream::wxZipOutputStream( wxOutputStream& stream,
                                      int level      /*=-1*/,
                                      wxMBConv& conv /*=wxConvLocal*/ )
  : wxArchiveOutputStream( stream, conv ) {
  Init( level );
}

wxZipOutputStream::wxZipOutputStream( wxOutputStream *stream,
                                      int level      /*=-1*/,
                                      wxMBConv& conv /*=wxConvLocal*/ )
  : wxArchiveOutputStream( stream, conv ) {
  Init( level );
}

void wxZipOutputStream::Init( int level ) {
  m_store = new wxStoredOutputStream( *m_parent_o_stream );
  m_deflate = NULL;
  m_backlink = NULL;
  m_initialData = new char[OUTPUT_LATENCY];
  m_initialSize = 0;
  m_pending = NULL;
  m_raw = false;
  m_headerOffset = 0;
  m_headerSize = 0;
  m_entrySize = 0;
  m_comp = NULL;
  m_level = level;
  m_offsetAdjustment = wxInvalidOffset;
}

wxZipOutputStream::~wxZipOutputStream() {
  Close();
  WX_CLEAR_LIST( wxZipEntryList_, m_entries );
  delete m_store;
  delete m_deflate;
  delete m_pending;
  delete [] m_initialData;
  if( m_backlink ) {
    m_backlink->Release( this );
  }
}

bool wxZipOutputStream::PutNextEntry(
  const wxString& name,
  const wxDateTime& dt /*=wxDateTime::Now()*/,
  wxFileOffset size    /*=wxInvalidOffset*/ ) {
  return PutNextEntry( new wxZipEntry( name, dt, size ) );
}

bool wxZipOutputStream::PutNextDirEntry(
  const wxString& name,
  const wxDateTime& dt /*=wxDateTime::Now()*/ ) {
  wxZipEntry *entry = new wxZipEntry( name, dt );
  entry->SetIsDir();
  return PutNextEntry( entry );
}

bool wxZipOutputStream::CopyEntry( wxZipEntry *entry,
                                   wxZipInputStream& inputStream ) {
  wxZipEntryPtr_ e( entry );
  return
    inputStream.DoOpen( e.get(), true ) &&
    DoCreate( e.release(), true ) &&
    Write( inputStream ).IsOk() && inputStream.Eof();
}

bool wxZipOutputStream::PutNextEntry( wxArchiveEntry *entry ) {
  wxZipEntry *zipEntry = wxStaticCast( entry, wxZipEntry );
  if( !zipEntry ) {
    delete entry;
  }
  return PutNextEntry( zipEntry );
}

bool wxZipOutputStream::CopyEntry( wxArchiveEntry *entry,
                                   wxArchiveInputStream& stream ) {
  wxZipEntry *zipEntry = wxStaticCast( entry, wxZipEntry );
  if( !zipEntry || !stream.OpenEntry( *zipEntry ) ) {
    delete entry;
    return false;
  }
  return CopyEntry( zipEntry, wx_static_cast( wxZipInputStream&, stream ) );
}

bool wxZipOutputStream::CopyArchiveMetaData( wxZipInputStream& inputStream ) {
  m_Comment = inputStream.GetComment();
  if( m_backlink ) {
    m_backlink->Release( this );
  }
  m_backlink = inputStream.MakeLink( this );
  return true;
}

bool wxZipOutputStream::CopyArchiveMetaData( wxArchiveInputStream& stream ) {
  return CopyArchiveMetaData( wx_static_cast( wxZipInputStream&, stream ) );
}

void wxZipOutputStream::SetLevel( int level ) {
  if( level != m_level ) {
    if( m_comp != m_deflate ) {
      delete m_deflate;
    }
    m_deflate = NULL;
    m_level = level;
  }
}

bool wxZipOutputStream::DoCreate( wxZipEntry *entry, bool raw /*=false*/ ) {
  CloseEntry();
  m_pending = entry;
  if( !m_pending ) {
    return false;
  }
  // write the signature bytes right away
  wxDataOutputStream ds( *m_parent_o_stream );
  ds << LOCAL_MAGIC;
  // and if this is the first entry test for seekability
  if( m_headerOffset == 0 && m_parent_o_stream->IsSeekable() ) {
    #if wxUSE_LOG
    bool logging = wxLog::IsEnabled();
    wxLogNull nolog;
    #endif // wxUSE_LOG
    wxFileOffset here = m_parent_o_stream->TellO();
    if( here != wxInvalidOffset && here >= 4 ) {
      if( m_parent_o_stream->SeekO( here - 4 ) == here - 4 ) {
        m_offsetAdjustment = here - 4;
        #if wxUSE_LOG
        wxLog::EnableLogging( logging );
        #endif // wxUSE_LOG
        m_parent_o_stream->SeekO( here );
      }
    }
  }
  m_pending->SetOffset( m_headerOffset );
  m_crcAccumulator = crc32( 0, Z_NULL, 0 );
  if( raw ) {
    m_raw = true;
  }
  m_lasterror = wxSTREAM_NO_ERROR;
  return true;
}

// Can be overriden to add support for additional compression methods
//
wxOutputStream *wxZipOutputStream::OpenCompressor(
  wxOutputStream& stream,
  wxZipEntry& entry,
  const Buffer bufs[] ) {
  if( entry.GetMethod() == wxZIP_METHOD_DEFAULT ) {
    if( GetLevel() == 0
        && ( IsParentSeekable()
             || entry.GetCompressedSize() != wxInvalidOffset
             || entry.GetSize() != wxInvalidOffset ) ) {
      entry.SetMethod( wxZIP_METHOD_STORE );
    } else {
      int size = 0;
      for( int i = 0; bufs[i].m_data; ++i ) {
        size += bufs[i].m_size;
      }
      entry.SetMethod( size <= 6 ?
                       wxZIP_METHOD_STORE : wxZIP_METHOD_DEFLATE );
    }
  }
  switch( entry.GetMethod() ) {
    case wxZIP_METHOD_STORE:
      if( entry.GetCompressedSize() == wxInvalidOffset ) {
        entry.SetCompressedSize( entry.GetSize() );
      }
      return m_store;
    case wxZIP_METHOD_DEFLATE: {
      int defbits = wxZIP_DEFLATE_NORMAL;
      switch( GetLevel() ) {
        case 0:
        case 1:
          defbits = wxZIP_DEFLATE_SUPERFAST;
          break;
        case 2:
        case 3:
        case 4:
          defbits = wxZIP_DEFLATE_FAST;
          break;
        case 8:
        case 9:
          defbits = wxZIP_DEFLATE_EXTRA;
          break;
      }
      entry.SetFlags( ( entry.GetFlags() & ~wxZIP_DEFLATE_MASK ) |
                      defbits | wxZIP_SUMS_FOLLOW );
      if( !m_deflate ) {
        m_deflate = new wxZlibOutputStream2( stream, GetLevel() );
      } else
      { m_deflate->Open( stream ); }
      return m_deflate;
    }
    default:
      wxLogError( _( "unsupported Zip compression method" ) );
  }
  return NULL;
}

bool wxZipOutputStream::CloseCompressor( wxOutputStream *comp ) {
  if( comp == m_deflate ) {
    m_deflate->Close();
  } else if( comp != m_store ) {
    delete comp;
  }
  return true;
}

// This is called when OUPUT_LATENCY bytes has been written to the
// wxZipOutputStream to actually create the zip entry.
//
void wxZipOutputStream::CreatePendingEntry( const void *buffer, size_t size ) {
  wxASSERT( IsOk() && m_pending && !m_comp );
  wxZipEntryPtr_ spPending( m_pending );
  m_pending = NULL;
  Buffer bufs[] = {
    { m_initialData, m_initialSize },
    { ( const char* )buffer, size },
    { NULL, 0 }
  };
  if( m_raw ) {
    m_comp = m_store;
  } else
    m_comp = OpenCompressor( *m_store, *spPending,
                             m_initialSize ? bufs : bufs + 1 );
  if( IsParentSeekable()
      || ( spPending->m_Crc
           && spPending->m_CompressedSize != wxInvalidOffset
           && spPending->m_Size != wxInvalidOffset ) ) {
    spPending->m_Flags &= ~wxZIP_SUMS_FOLLOW;
  } else if( spPending->m_CompressedSize != wxInvalidOffset ) {
    spPending->m_Flags |= wxZIP_SUMS_FOLLOW;
  }
  m_headerSize = spPending->WriteLocal( *m_parent_o_stream, GetConv() );
  m_lasterror = m_parent_o_stream->GetLastError();
  if( IsOk() ) {
    m_entries.push_back( spPending.release() );
    OnSysWrite( m_initialData, m_initialSize );
  }
  m_initialSize = 0;
}

// This is called to write out the zip entry when Close has been called
// before OUTPUT_LATENCY bytes has been written to the wxZipOutputStream.
//
void wxZipOutputStream::CreatePendingEntry() {
  wxASSERT( IsOk() && m_pending && !m_comp );
  wxZipEntryPtr_ spPending( m_pending );
  m_pending = NULL;
  m_lasterror = wxSTREAM_WRITE_ERROR;
  if( !m_raw ) {
    // Initially compresses the data to memory, then fall back to 'store'
    // if the compressor makes the data larger rather than smaller.
    wxMemoryOutputStream mem;
    Buffer bufs[] = { { m_initialData, m_initialSize }, { NULL, 0 } };
    wxOutputStream *comp = OpenCompressor( mem, *spPending, bufs );
    if( !comp ) {
      return;
    }
    if( comp != m_store ) {
      bool ok = comp->Write( m_initialData, m_initialSize ).IsOk();
      CloseCompressor( comp );
      if( !ok ) {
        return;
      }
    }
    m_entrySize = m_initialSize;
    m_crcAccumulator = crc32( 0, ( Byte* )m_initialData, m_initialSize );
    if( mem.GetSize() > 0 && mem.GetSize() < m_initialSize ) {
      m_initialSize = mem.GetSize();
      mem.CopyTo( m_initialData, m_initialSize );
    } else
    { spPending->SetMethod( wxZIP_METHOD_STORE ); }
    spPending->SetSize( m_entrySize );
    spPending->SetCrc( m_crcAccumulator );
    spPending->SetCompressedSize( m_initialSize );
  }
  spPending->m_Flags &= ~wxZIP_SUMS_FOLLOW;
  m_headerSize = spPending->WriteLocal( *m_parent_o_stream, GetConv() );
  if( m_parent_o_stream->IsOk() ) {
    m_entries.push_back( spPending.release() );
    m_comp = m_store;
    m_store->Write( m_initialData, m_initialSize );
  }
  m_initialSize = 0;
  m_lasterror = m_parent_o_stream->GetLastError();
}

// Write the 'central directory' and the 'end-central-directory' records.
//
bool wxZipOutputStream::Close() {
  CloseEntry();
  if( m_lasterror == wxSTREAM_WRITE_ERROR || m_entries.size() == 0 ) {
    wxFilterOutputStream::Close();
    return false;
  }
  wxZipEndRec endrec;
  endrec.SetEntriesHere( m_entries.size() );
  endrec.SetTotalEntries( m_entries.size() );
  endrec.SetOffset( m_headerOffset );
  endrec.SetComment( m_Comment );
  wxZipEntryList_::iterator it;
  wxFileOffset size = 0;
  for( it = m_entries.begin(); it != m_entries.end(); ++it ) {
    size += ( *it )->WriteCentral( *m_parent_o_stream, GetConv() );
    delete *it;
  }
  m_entries.clear();
  endrec.SetSize( size );
  endrec.Write( *m_parent_o_stream, GetConv() );
  m_lasterror = m_parent_o_stream->GetLastError();
  if( !wxFilterOutputStream::Close() || !IsOk() ) {
    return false;
  }
  m_lasterror = wxSTREAM_EOF;
  return true;
}

// Finish writing the current entry
//
bool wxZipOutputStream::CloseEntry() {
  if( IsOk() && m_pending ) {
    CreatePendingEntry();
  }
  if( !IsOk() ) {
    return false;
  }
  if( !m_comp ) {
    return true;
  }
  CloseCompressor( m_comp );
  m_comp = NULL;
  wxFileOffset compressedSize = m_store->TellO();
  wxZipEntry& entry = *m_entries.back();
  // When writing raw the crc and size can't be checked
  if( m_raw ) {
    m_crcAccumulator = entry.GetCrc();
    m_entrySize = entry.GetSize();
  }
  // Write the sums in the trailing 'data descriptor' if necessary
  if( entry.m_Flags & wxZIP_SUMS_FOLLOW ) {
    wxASSERT( !IsParentSeekable() );
    m_headerOffset +=
      entry.WriteDescriptor( *m_parent_o_stream, m_crcAccumulator,
                             compressedSize, m_entrySize );
    m_lasterror = m_parent_o_stream->GetLastError();
  }
  // If the local header didn't have the correct crc and size written to
  // it then seek back and fix it
  else if( m_crcAccumulator != entry.GetCrc()
           || m_entrySize != entry.GetSize()
           || compressedSize != entry.GetCompressedSize() ) {
    if( IsParentSeekable() ) {
      wxFileOffset here = m_parent_o_stream->TellO();
      wxFileOffset headerOffset = m_headerOffset + m_offsetAdjustment;
      m_parent_o_stream->SeekO( headerOffset + SUMS_OFFSET );
      entry.WriteDescriptor( *m_parent_o_stream, m_crcAccumulator,
                             compressedSize, m_entrySize );
      m_parent_o_stream->SeekO( here );
      m_lasterror = m_parent_o_stream->GetLastError();
    } else
    { m_lasterror = wxSTREAM_WRITE_ERROR; }
  }
  m_headerOffset += m_headerSize + compressedSize;
  m_headerSize = 0;
  m_entrySize = 0;
  m_store->Close();
  m_raw = false;
  if( IsOk() ) {
    m_lasterror = m_parent_o_stream->GetLastError();
  } else
    wxLogError( _( "error writing zip entry '%s': bad crc or length" ),
                entry.GetName().c_str() );
  return IsOk();
}

void wxZipOutputStream::Sync() {
  if( IsOk() && m_pending ) {
    CreatePendingEntry( NULL, 0 );
  }
  if( !m_comp ) {
    m_lasterror = wxSTREAM_WRITE_ERROR;
  }
  if( IsOk() ) {
    m_comp->Sync();
    m_lasterror = m_comp->GetLastError();
  }
}

size_t wxZipOutputStream::OnSysWrite( const void *buffer, size_t size ) {
  if( IsOk() && m_pending ) {
    if( m_initialSize + size < OUTPUT_LATENCY ) {
      memcpy( m_initialData + m_initialSize, buffer, size );
      m_initialSize += size;
      return size;
    } else
    { CreatePendingEntry( buffer, size ); }
  }
  if( !m_comp ) {
    m_lasterror = wxSTREAM_WRITE_ERROR;
  }
  if( !IsOk() || !size ) {
    return 0;
  }
  if( m_comp->Write( buffer, size ).LastWrite() != size ) {
    m_lasterror = wxSTREAM_WRITE_ERROR;
  }
  m_crcAccumulator = crc32( m_crcAccumulator, ( Byte* )buffer, size );
  m_entrySize += m_comp->LastWrite();
  return m_comp->LastWrite();
}
